<!doctype html>
<meta charset=utf-8>
<title>Tests for CSS animation event order</title>
<link rel="help" href="https://drafts.csswg.org/css-animations-2/#event-dispatch"/>
<script src="../testcommon.js"></script>
<style>
  @keyframes anim {
    from { margin-left: 0px; }
    to { margin-left: 100px; }
  }
</style>
<body>
<script type='text/javascript'>
'use strict';

/**
 * Asserts that the set of actual and received events match.
 * @param actualEvents   An array of the received AnimationEvent objects.
 * @param expectedEvents A series of array objects representing the expected
 *        events, each having the form:
 *          [ event type, target element, elapsed time ]
 */
function checkEvents(actualEvents, ...expectedEvents) {
  assert_equals(actualEvents.length, expectedEvents.length,
                `Number of actual events (${actualEvents.length}: \
${actualEvents.map(event => event.type).join(', ')}) should match expected \
events (${expectedEvents.map(event => event.type).join(', ')})`);

  actualEvents.forEach((actualEvent, i) => {
    assert_equals(expectedEvents[i][0], actualEvent.type,
                  'Event type should match');
    assert_equals(expectedEvents[i][1], actualEvent.target,
                  'Event target should match');
    assert_equals(expectedEvents[i][2], actualEvent.elapsedTime,
                  'Event\'s elapsed time should match');
  });
}

function setupAnimation(t, animationStyle, receiveEvents) {
  const div = addDiv(t, { style: "animation: " + animationStyle });
  const watcher = new EventWatcher(t, div, [ 'animationstart',
                                             'animationiteration',
                                             'animationend' ]);

  ['start', 'iteration', 'end'].forEach(name => {
    div['onanimation' + name] = function(evt) {
    receiveEvents.push({ type:        evt.type,
                         target:      evt.target,
                         elapsedTime: evt.elapsedTime });
    }.bind(this);
  });

  const animation = div.getAnimations()[0];

  return [animation, watcher, div];
}

promise_test(function(t) {
  let events = [];
  const [animation1, watcher1, div1] =
    setupAnimation(t, 'anim 100s 2 paused', events);
  const [animation2, watcher2, div2] =
    setupAnimation(t, 'anim 100s 2 paused', events);

  return Promise.all([ watcher1.wait_for('animationstart'),
                       watcher2.wait_for('animationstart') ]).then(function() {
    checkEvents(events, ['animationstart', div1, 0],
                        ['animationstart', div2, 0]);

    events.length = 0;  // Clear received event array

    animation1.currentTime = 100 * MS_PER_SEC;
    animation2.currentTime = 100 * MS_PER_SEC;
    return Promise.all([ watcher1.wait_for('animationiteration'),
                         watcher2.wait_for('animationiteration') ]);
  }).then(function() {
    checkEvents(events, ['animationiteration', div1, 100],
                        ['animationiteration', div2, 100]);

    events.length = 0;  // Clear received event array

    animation1.finish();
    animation2.finish();

    return Promise.all([ watcher1.wait_for('animationend'),
                         watcher2.wait_for('animationend') ]);
  }).then(function() {
    checkEvents(events, ['animationend', div1, 200],
                        ['animationend', div2, 200]);
  });
}, 'Test same events are ordered by elements.');

promise_test(function(t) {
  let events = [];
  const [animation1, watcher1, div1] =
    setupAnimation(t, 'anim 200s 400s', events);
  const [animation2, watcher2, div2] =
    setupAnimation(t, 'anim 300s 2', events);

  return watcher2.wait_for('animationstart').then(function(evt) {
    animation1.currentTime = 400 * MS_PER_SEC;
    animation2.currentTime = 400 * MS_PER_SEC;

    events.length = 0;  // Clear received event array

    return Promise.all([ watcher1.wait_for('animationstart'),
                         watcher2.wait_for('animationiteration') ]);
  }).then(function() {
    checkEvents(events, ['animationiteration', div2, 300],
                        ['animationstart',     div1, 0]);
  });
}, 'Test start and iteration events are ordered by time.');

promise_test(function(t) {
  let events = [];
  const [animation1, watcher1, div1] =
    setupAnimation(t, 'anim 150s', events);
  const [animation2, watcher2, div2] =
    setupAnimation(t, 'anim 100s 2', events);

  return Promise.all([ watcher1.wait_for('animationstart'),
                       watcher2.wait_for('animationstart') ]).then(function() {
    animation1.currentTime = 150 * MS_PER_SEC;
    animation2.currentTime = 150 * MS_PER_SEC;

    events.length = 0;  // Clear received event array

    return Promise.all([ watcher1.wait_for('animationend'),
                         watcher2.wait_for('animationiteration') ]);
  }).then(function() {
    checkEvents(events, ['animationiteration', div2, 100],
                        ['animationend',       div1, 150]);
  });
}, 'Test iteration and end events are ordered by time.');

promise_test(function(t) {
  let events = [];
  const [animation1, watcher1, div1] =
    setupAnimation(t, 'anim 100s 100s', events);
  const [animation2, watcher2, div2] =
    setupAnimation(t, 'anim 100s 2', events);

  animation1.finish();
  animation2.finish();

  return Promise.all([ watcher1.wait_for([ 'animationstart',
                                           'animationend' ]),
                       watcher2.wait_for([ 'animationstart',
                                           'animationend' ]) ]).then(function() {
    checkEvents(events, ['animationstart', div2, 0],
                        ['animationstart', div1, 0],
                        ['animationend',   div1, 100],
                        ['animationend',   div2, 200]);
  });
}, 'Test start and end events are sorted correctly when fired simultaneously');

done();
</script>
</body>
</html>
